/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement the simple type system for AMC.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include <stdbool.h>

#include "../include/amcc_error.h"
#include "../include/amcc_log.h"
#include "../include/typesys.h"
#include "../include/symentry.h"

typedef struct type_system {

    // pointer to inner type pool
    t_hashmap_ptr  typePoolPtr;

    // pointer to another type pool only for expr tree
    t_treemap_ptr  exprTypePoolPtr;

} t_typesys, * t_typesys_ptr;

// module level data
static t_typesys  gTypeSystem;


extern t_hashmap_ptr  typepool_new();
extern void       typepool_destroy(t_hashmap_ptr);
extern t_type_ptr typepool_put(t_hashmap_ptr, t_type_ptr typePtr);
extern t_type_ptr typepool_find(t_hashmap_ptr, const char* typeName);

extern t_treemap_ptr  expr_typepool_new();
extern void  expr_typepool_destroy(t_treemap_ptr);

/** initialize global data of type-system module */
int typesys_module_init () {
    gTypeSystem.typePoolPtr = typepool_new();
    type_new_baseType(TK_TYPE_INT);
    type_new_baseType(TK_TYPE_DOUBLE);
    type_new_baseType(TK_TYPE_VOID);

    gTypeSystem.exprTypePoolPtr = expr_typepool_new();
    return 0;
}

/** clean global data of type-system module */
void typesys_module_clean () {
    expr_typepool_destroy(gTypeSystem.exprTypePoolPtr);
    gTypeSystem.exprTypePoolPtr = NULL;

    typepool_destroy(gTypeSystem.typePoolPtr);
    gTypeSystem.typePoolPtr     = NULL;
}

t_treemap_ptr  typesys_get_exprTypePool () {
    return gTypeSystem.exprTypePoolPtr;
}


static size_t type_size_base_(EnumTokenKind type);
static size_t type_size_array_(t_type_ptr elementType, const int * dimensions);
#define type_size_pointer_()    sizeof(char*)

static void type_make_name_var_(t_type_ptr baseType, int * dimensions, char * pBuffer);
static void type_make_name_func_(t_type_ptr returnType, t_list_ptr paramListPtr, char * pBuffer);


// Create the type object for basic types
t_type_ptr type_new_baseType (EnumTokenKind baseTypeId) {
    char buff[50]; // should be enough
    strcpy(buff, type_name_basic(baseTypeId));

    // Firstly, find it in pool
    t_type_ptr pType = typepool_find(gTypeSystem.typePoolPtr, buff);
    if (NULL != pType) {
        // log_trace("    return EXISTing base type: %s, id %tx", buff, pType);
        return pType;
    }

    // Secondly, create a new object ONLY it does not existing in pool
    pType = calloc(1, sizeof(t_type));
    pType->typeKind  = AMCC_TYPE_BASE;
    pType->typeName  = cstring_duplicate(buff);
    pType->width     = type_size_base_(baseTypeId);
    pType->content.baseType.baseType = baseTypeId;
    pType = typepool_put(gTypeSystem.typePoolPtr, pType);

    // log_trace("    return NEW-CREATED base type: %s, id %tx", buff,  pType);
    return pType;
}


// Creates a type object for a scalar or an array
t_type_ptr type_new_varType (t_type_ptr baseType, int * dims) {
    char buff[500]; // should be enough
    type_make_name_var_(baseType, dims, buff);

    // Firstly, find it in pool
    t_type_ptr pType = typepool_find(gTypeSystem.typePoolPtr, buff);
    if (NULL != pType) {
        log_trace("    return EXISTing var type: %s, id %tx", buff, pType);
        return pType;
    }

    // Secondly, create a new object ONLY it does not existing in pool
    pType = calloc(1, sizeof(t_type));
    pType->typeKind  = AMCC_TYPE_ARRAY;
    pType->typeName  = cstring_duplicate(buff);
    pType->width     = type_size_array_(baseType, dims);
    pType->content.arrType.baseType = baseType;
    if (NULL != dims && dims[0] > 0) {
        int  nSlots = dims[0] + 1;
        pType->content.arrType.dimensions = calloc(nSlots, sizeof(int));
        memcpy(pType->content.arrType.dimensions, dims, sizeof(int) * nSlots);
    }

    // Like C, an array is stored in "row major" layout.
    // So this constant part always is Zero.
    pType->content.arrType.const_address = 0;
    pType = typepool_put(gTypeSystem.typePoolPtr, pType);

    log_trace("    return NEW-CREATED var type: %s, id %tx", buff,  pType);
    return pType;
}


t_type_ptr type_new_funcType (t_type_ptr returnType, t_list_ptr paramListPtr) {
    char buff[500]; // should be enough
    type_make_name_func_(returnType, paramListPtr, buff);

    // Firstly, find it in pool
    t_type_ptr pType = typepool_find(gTypeSystem.typePoolPtr, buff);
    if (NULL != pType) {
        log_trace("    return EXISTing func type: %s, id %tx", buff, pType);
        return pType;
    }

    // Secondly, create a new object ONLY it does not existing in pool
    pType = calloc(1, sizeof(t_type));
    pType->typeKind  = AMCC_TYPE_FUNC;
    pType->typeName  = cstring_duplicate(buff);
    pType->width     = type_size_pointer_() ;
    pType->content.funcType.returnType = returnType;

    // Thirdly, collect parameters' types
    t_list_ptr parameterTypes = &(pType->content.funcType.parameterTypes);
    list_init( parameterTypes, NULL );
    if ( list_size(paramListPtr) > 0 )  // exists parameters
    {
        t_list_iterator itr;
        LIST_ITERATOR_INIT(&itr, paramListPtr);
        t_list_node_ptr listNode = LIST_ITERATOR_BEGIN(&itr);
        t_list_node_ptr listEnd = LIST_ITERATOR_END(&itr);
        for (; listNode != listEnd; listNode = LIST_ITERATOR_NEXT(&itr)) {
            t_symentry_ptr paramPtr = (t_symentry_ptr) list_node_get_value(listNode);
            list_append(parameterTypes, symentry_type(paramPtr));
        }
    }

    pType = typepool_put(gTypeSystem.typePoolPtr, pType);

    log_trace("    return NEW-CREATED func type: %s, id %tx", buff, pType);
    return pType;
}


/**
 * @brief Destroy a given type object.
 * @param pType  pointer to a type object tobe destroyed
 *
 * @implements ONLY the type-pool can destroy a type object.
 */
void type_destroy (t_type_ptr  pType) {
    if (NULL == pType) return;
    if ( AMCC_TYPE_FUNC == pType->typeKind ) {
        log_trace("  destroy type of func: %tx, %s", pType, type_name(pType));
        list_clean( &(pType->content.funcType.parameterTypes) );
    } else if (AMCC_TYPE_ARRAY == pType->typeKind ) {
        log_trace("  destroy type of  var: %tx, %s", pType, type_name(pType));
        free ( pType->content.arrType.dimensions );
    } else if ( AMCC_TYPE_BASE == pType->typeKind ) {
        log_trace("  destroy type of base: %tx, %s", pType, type_name(pType));
    }

    free(pType->typeName);

    free(pType);
}



// -----------------------------------------------------------------------
//  Helper functions to support main operation of class type
//


// Return the internal indicative name  for a given basic type
const char * type_name_basic (EnumTokenKind typeEnum) {
    switch ( typeEnum ) {
        case TK_TYPE_INT     : return  "int";
        case TK_TYPE_DOUBLE  : return  "double";
        case TK_TYPE_VOID    : return  "void";
        default :
            break;
    }

    return token_sampleOfKind(typeEnum);
}


// Get the size of a given basic type
static size_t type_size_base_ (EnumTokenKind type) {
    switch ( type ) {
        case TK_TYPE_INT     : return  sizeof(int);
        case TK_TYPE_DOUBLE  : return  sizeof(double);
        case TK_TYPE_VOID    : return  0;
        default :
            break;
    }
    ERROR_REPORT_BUG("Unsupported type '%s' to calculate memory size",
                     token_sampleOfKind(type));
    return  1; // not 0 : prevent AMCC from CRASHing
}


// Calculates the size of an array
static size_t type_size_array_ (t_type_ptr elementType, const int * dimensions) {
    size_t sz_basic  = type_width(elementType);
    if (NULL == dimensions || dimensions[0] < 1)  // a scalar variable
        return sz_basic;

    // If the length of the first dim is unknown,
    // we treat it as a pointer.
    if (dimensions[1] < 1)
        return type_size_pointer_();

    size_t sizeTotal = sz_basic;
    int nDimensions  = dimensions[0];
    for(int dimCounter = 1; dimCounter <= nDimensions; ++dimCounter)
    {
        sizeTotal *= dimensions[dimCounter];
    }

    return sizeTotal;
}


// Generates the internal indicative type name for a scalar or array
static void type_make_name_var_ (
        t_type_ptr baseType, // scalar type, element type of array
        int  * dimensions,   // dimension info for an array
        char * pBuffer)      // output name
{
    strcpy(pBuffer, baseType->typeName);

    // append the type for array dimensions, like [N1,N2,...]
    if (NULL != dimensions && dimensions[0] > 0) {
        int    dim = 1;
        char * p   = pBuffer + strlen(pBuffer);
        strcat(p++, "[");
        for( ; dim <= dimensions[0]; ++dim) {
            sprintf(p, "%s%d",
                    (dim == 1) ? "" : ",",
                    dimensions[dim]);
            p += strlen(p);
        }
        strcat(p, "]");
    }

    return ;
}


// Generates the internal indicative type name for a function
static void type_make_name_func_ (
        t_type_ptr returnType,      // type of the return value
        t_list_ptr paramListPtr,    // list of parameters with name and type
        char * pBuffer)
{
    char  * p = pBuffer;
    strcpy(p, returnType->typeName);
    strcat(p, "<-(");
    p += strlen(p);

    // collect type-names of parameters
    t_list_iterator  itr;
    LIST_ITERATOR_INIT( &itr, paramListPtr );
    t_list_node_ptr       e = LIST_ITERATOR_BEGIN( &itr );
    t_list_node_ptr listEnd = LIST_ITERATOR_END  ( &itr );
    for( ; e != listEnd ; e = LIST_ITERATOR_NEXT ( &itr ) ) {
        t_symentry_ptr paramPtr = (t_symentry_ptr)list_node_get_value( e );
        if (NULL == paramPtr) {
            ERROR_REPORT_BUG("NULL parameter type");
            continue;
        }
        strcat(p, type_name(symentry_type(paramPtr)) );
        strcat(p, ",");
        p += strlen(p);
    }
    if ( *(p-1) == ',' ) *(--p) = '\0';
    strcat(p, ")");

    return ;
}



// -----------------------------------------------------------------------
//  Attribute accessor  of class type
//


size_t type_width (t_type_ptr type) {
    if (NULL == type) return 0;
    return type->width;
}

const char * type_name (t_type_ptr type) {
    return (type==NULL) ? "UNKNOWN" : type->typeName;
}

EnumTypeKind type_kind (t_type_ptr pThis) {
    return (pThis == NULL) ? AMCC_TYPE_ERR : pThis->typeKind;
}


bool   type_is_scalar (t_type_ptr pThis) {
    if (NULL == pThis)
        return false;
    if (AMCC_TYPE_BASE == pThis->typeKind)
        return true;
    if (AMCC_TYPE_ARRAY != pThis->typeKind)
        return false;

    int * dimensions = pThis->content.arrType.dimensions;
    return (NULL == dimensions || dimensions[0] < 1);
}

bool   type_is_array (t_type_ptr pThis) {
    if (NULL == pThis || AMCC_TYPE_ARRAY != pThis->typeKind)
        return false;

    int * dimensions = pThis->content.arrType.dimensions;
    return (NULL != dimensions && dimensions[0] >= 1);
}

t_type_ptr type_var_base (t_type_ptr pThis) {
    if (NULL == pThis)
        return NULL;

    switch (pThis->typeKind) {
        case AMCC_TYPE_BASE:
            return pThis;
        case AMCC_TYPE_ARRAY:
            return pThis->content.arrType.baseType;
        default:
            return NULL;
    }
}

EnumTokenKind type_var_basekind (t_type_ptr pThis) {
    if (NULL == pThis)
        return TK_ERROR;

    switch (pThis->typeKind) {
        case AMCC_TYPE_BASE:
            return pThis->content.baseType.baseType;
        case AMCC_TYPE_ARRAY:
            return type_var_basekind(pThis->content.arrType.baseType);
        case AMCC_TYPE_FUNC:
            return type_var_basekind(pThis->content.funcType.returnType);
        default:
            return TK_ERROR;
    }
}


int type_var_dimcount (t_type_ptr pThis) {
    if (type_is_array(pThis)) {
        return pThis->content.arrType.dimensions[0];
    }

    return 0;
}

int type_var_dimlength (t_type_ptr pThis, int K) {
    if (type_is_array(pThis)) {
        int * dimensions = pThis->content.arrType.dimensions;
        if (1 <= K && K <= dimensions[0])
            return dimensions[K];
    }

    return 0;
}

const int * type_var_diminfo (t_type_ptr pThis) {
    if (type_is_array(pThis)) {
        return pThis->content.arrType.dimensions;
    }

    return NULL;
}

size_t type_var_constaddr (t_type_ptr pThis) {
    if (type_is_array(pThis))
        return pThis->content.arrType.const_address;
    else
        return 0;
}



bool   type_is_func (t_type_ptr pThis) {
    if (NULL == pThis) return false;

    return (AMCC_TYPE_FUNC == pThis->typeKind);
}

t_type_ptr type_func_return (t_type_ptr pThis) {
    if ( ! type_is_func(pThis) )
        return NULL;

    return pThis->content.funcType.returnType;
}

t_type_ptr type_func_paramK (t_type_ptr pThis, int K) {
    if ( ! type_is_func(pThis) )
        return NULL;

    t_list_ptr parameterTypes = &(pThis->content.funcType.parameterTypes);
    int n = list_size(parameterTypes);
    if ( K<1 || K>n )
        return NULL;

    return (t_type_ptr)list_get_value(parameterTypes, K-1);
}

t_list_ptr type_func_parameters (t_type_ptr pThis) {
    if ( ! type_is_func(pThis) )
        return NULL;

    return &(pThis->content.funcType.parameterTypes);
}
